package com.example.demo.config;


import com.example.demo.JwtTokenFilter;
import com.example.demo.handler.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
 * @author 三石
 */
@Configuration
@EnableWebSecurity     //启用 Spring Security 的必要注解之一
@EnableMethodSecurity
public class SecurityConfig {

    /**
     * 白名单
     * 这里可避开错误，凭证不能为空
     */
    public static final String[] WHITE_LIST = new String[]{
            "/login", "/logout", "/static", "/error","/Captcha/getCaptcha","/user/resetPassword","/user/uploadAvatar",
            "/login", "/logout", "/static", "/error","/static/image/**",
            "/actuator/health",
            "/actuator/info",
            "/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**",
            "/swagger-resources/**",
            "/api/list/**",
            "/api/add/**",
            "/api/update/**",
            "/api/delete/**",
            "/api/getApiInfoById",
            "/**"
    };
    /**
     * 配置Security加密方式
     * 默认密码加密使用 a54b91e8-b81f-48f8-970e-d472b0d2e46b
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 路径匹配方式
     */
    @Bean
    public AntPathMatcher antPathMatcher() {
            return new AntPathMatcher();
    }

    /**
     * SpringSecurity 跨域配置
     */
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // configuration.setAllowedOrigins(List.of("http://127.0.0.1:8080", "http://127.0.0.1:8848"));
        configuration.setAllowedOriginPatterns(List.of("*"));
        configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    /**
     * 配置Security授权规则
     * @param http HttpSecurity 对象，用于配置 HTTP 安全相关的内容。
     * @param antPathMatcher AntPathMatcher 对象，用于匹配 URL 路径，比如实现路径的匹配和匹配规则的设置等。
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http,
            AntPathMatcher antPathMatcher,  //匹配规则
            SecurityAuthenticationSuccessHandler securityAuthenticationSuccessHandler, //登录成功处理器
            SecurityAccessDeniedHandler securityAccessDeniedHandler, //登录后无权访问处理器
            SecurityAuthenticationFailureHandler securityAuthenticationFailureHandler, //登录失败
            SecurityAuthenticationEntryPointHandler securityAuthenticationEntryPointHandler,//未登录访问，无权限访问处理器
            SecurityLogoutSuccessHandler securityLogoutSuccessHandler,
            JwtTokenFilter jwtTokenFilter,
            CaptchaHandler captchaHandler,
            CorsConfigurationSource corsConfigurationSource
            ) throws Exception {
        // 关闭表单的防伪造请求（在表单中默认携带一个随机token字符串），因为前后端分离没办法保证token
        http.csrf().disable();

        //跨域
        http.cors().configurationSource(corsConfigurationSource);
        // 开启和配置表单登录方式
        http.formLogin(customizer -> customizer
                //登录成功后执行处理器（前后端分离)
                .successHandler(securityAuthenticationSuccessHandler)
                //登录失败后执行处理器
                .failureHandler(securityAuthenticationFailureHandler)
                .permitAll()
        );
        // 异常处理：登录成功，但是没有权限
        http.exceptionHandling(customizer -> customizer
                .authenticationEntryPoint(securityAuthenticationEntryPointHandler)  //不会跳转页面
                .accessDeniedHandler(securityAccessDeniedHandler)
        );
        // 退出策略
        http.logout(customizer -> customizer
                //.addLogoutHandler(securityLogoutHandler)
                .invalidateHttpSession(true) //退出后清空当前用户的session
                .clearAuthentication(true) //退出后从上下文清除清空当前用户的认证凭证
                .logoutSuccessHandler(securityLogoutSuccessHandler)
                .permitAll()
        );

        //配置过滤器  在UsernamePasswordAuthenticationFilter类之前执行
        http
       .addFilterBefore(captchaHandler, UsernamePasswordAuthenticationFilter.class)
       .addFilterBefore(jwtTokenFilter,UsernamePasswordAuthenticationFilter.class);


        // 对Security设置授权规则
        http.authorizeHttpRequests(customizer -> customizer
                // 白名单：静态资源，无条件允许
                .requestMatchers(WHITE_LIST).permitAll()
                // .anyRequest().authenticated()
                // 其他所有资源，通过自定义规则授权
                .anyRequest().access(new AuthorizationManager<RequestAuthorizationContext>() {
                    /**
                     * @param authentication 权限对象
                     * @param object 请求对象
                     * @return  “权限对象” 和 “请求对象” 验证
                     */
                    @Override
                    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
                        Collection<? extends GrantedAuthority> authorities = authentication.get().getAuthorities();
                        //使用stream流循环判断authorities的每一个权限规则
                    boolean  isMatch = authorities.stream().anyMatch(new Predicate<GrantedAuthority>() {
                            @Override
                            public boolean test(GrantedAuthority grantedAuthority) {
                                /**
                                 * grantedAuthority.getAuthority() 匹配规则
                                 * object.getRequest().getRequestURI() 匹配路径（前端提交）
                                 * grantedAuthority.getAuthority() 权限规则转为字符串
                                 */
                               return antPathMatcher.match(grantedAuthority.getAuthority(),object.getRequest().getRequestURI());
                            }
                        });
                    return new AuthorizationDecision(isMatch);
                    }
                }));

        // Session管理策略
        http.sessionManagement(customizer -> customizer
                .sessionCreationPolicy(SessionCreationPolicy.NEVER)
        );
        return http.build();
    }
}
